查看原文
其他

使用Grunt实现资源自动化同步

张晓衡 Creator星球游戏开发社区 2021-08-09

同步美术、策划资源是日常开发中极为频繁的事情,shawn借用Web前端的一些思想和工具,将Grunt自动化框架引入Cocos Creator项目,可以实现相对高效地将图片、动画、配置、音效等游戏资源导入到客户端工程中。

grunt介绍

在开始之前先用简单介绍一下Grunt是什么:

为什么选择使用Grunt做自动化工具,简单总结以下几点:

  1. 使用JavaScript语言,与Cocos Creator开发使用相同的语言,减少学习成本

  2. 插件丰富,6000+(本篇文章只介绍两个grunt-sync和grunt-shell)

  3. 使用json配置插件完成任务,没有太多逻辑条件,使用简单容易上手,当配置好一个模块后,其它模块可以依葫芦画瓢,策划人员也可以上手配置

  4. Grunt基于Nodejs,可以借用大量插件与npm模块实现各种复杂需求

  5. 跨平台

安装grunt与插件

首先,使用npm安装全局grunt-cli工具:

  1. >npm install grunt-cli -g

然后在项目根目录初始化npm的包管理文件package.json:

  1. >npm init

输入npm init后一路回车,然后在项目中安装grunt npm模块:

  1. >npm install grunt --save-dev

grunt只是一个自动化框架,我们这里还需要安装上面说的两个插件

  1. >npm install grunt-sync --save-dev   //文件同步插件

  2. >npm install grunt-shell --save-dev  //shell插件

Grunt任务模块目录结构

安装好Grunt的命令行、插件后,在项目根目录创建Gruntfile.js文件,这是Grunt自动任务的入口文件。同时在根项目新建一个tools目录,用于存放各种与项目有关的工具或脚本,再添加一个grunt-task目录,用于存放具体的grunt任务配置脚本,请看下图:

上图中xxx-task.js就是各子模块的自动化任务。

Gruntfile

shawn在早期使用Grunt时,将所有任务都编写在Gruntfile.js文件,当模块越来越多,维护起来越来越困难,因此将不同模块的自动化任务独立开来,在Gruntfile.js进行统一加载和任务注册,下面看下Gruntfile文件的内容:

  1. //引入rd模块读取文件

  2. let rd = require('rd');

  3. //获./tools/grunt-task目录下取所有文件

  4. let taskScripts = rd.readFileSync('./tools/grunt-task');


  5. module.exports = (grunt) => {

  6.    //我们这里使用了grunt-shel&grunt-sync插件

  7.    //下面shell与sync对象分别用于收集两种任务配置

  8.    let shell = {};

  9.    let sync = {};


  10.    //将grunt设置为全局变量

  11.    global.grunt = grunt;


  12.    //require所有任务模块,放入tasks数组

  13.    let tasks = [];

  14.    taskScripts.forEach((script) => {

  15.        let task = require(script);

  16.        if (task.init) {

  17.            //让task对象自己填充sync和shell内容

  18.            task.init(sync, shell);

  19.            tasks.push(task);

  20.        }

  21.    });

  22.    //配置sync、shell两大任务

  23.    grunt.initConfig({

  24.        sync,

  25.        shell,

  26.    });


  27.    //注册grunt-shell插件,用于执行外部shell命令

  28.    grunt.loadNpmTasks('grunt-shell');

  29.    //注册grunt-sync插件,用于本地文件同步

  30.    grunt.loadNpmTasks('grunt-sync');


  31.    //注册自定义的grunt任务

  32.    tasks.forEach(task => task.registerTask());

  33. };

简单说明一下:

  1. 加载tools/grunt-task下所有任务脚本

  2. 为每个任务对象传入sync、shell两个任务集合对象,各任务模块在sync、shell对象中配置自己的任务内容。

  3. 使用了两个grunt插件:grunt-shell、grunt-sync

  4. 最后调用所有task.registerTask()将命令注册到grunt命令行

在命令控制台上执行grunt --help会看到我们所编写的自动化任务,下图是shawn曾经项目中创建的Grunt任务:

文件同步任务

我们现在去执行 grunt--help还是空的,还没有注册具体的任务,前面讲过,Grunt是使用插件 + JSON配置的方式来创建任务,我们看一个美术资源为例:

资源仓库客户端Assets按上图所示建立对应关系,其中绿色箭头是文件同步操作,其中headIcon目录中的图片,在项目中是动态加载的,需要同步到assets/resources/game1/texture目录下,这些操作我们可以使用grunt-sycn来完成。

其中比较特别是atlas目录,里面是经过分类需要合并图集的图片,文件合并后放到客户端项目assets/game1/texture/atlas目录,这个操作shawn是借用TexturePacker命令行工具 + Nodejs脚本来完成。

UI资源同步

梳理好了资源目录结构,现在我们将上流程编写成grunt同步任务,创建一个game1-task.js的文件,内容如下:

  1. let path = require('path');

  2. //获取grunt参数,是否模拟执行,不真实复制文件

  3. let pretend = grunt.option('pretend');

  4. //获取资源根路径,资源路径是定义在单独的define.js文件

  5. let { UI_ROOT_PATH,ANI_ROOT_PATH } = require('./define');

  6. //定义模块名,也就是在assets下的目录名

  7. const moduleName = 'game1';

  8. //定义同步任务

  9. const syncTask = {

  10.    //同步UI任务:普通图片、背景图片

  11.    'sync-ui': {

  12.        files: [

  13.            //同步UI图片

  14.            {

  15.                //设置过滤器,排除atlas、headIcon、svn、隐藏文件

  16.                src: ['**', '!atlas/', '!headIcon/', '!**/.*', '!**/.svn', '!**/.svn/**'],  

  17.                dest: `./assets/${moduleName}/texture/ui`,  //目标路径,将文件同步到此处

  18.                cwd: path.join(UI_ROOT_PATH, moduleName, 'ui')  //美术UI资源目录

  19.            },

  20.            //同步headIcon

  21.            {

  22.                //排除 atlas 目录及下面的子目录、文件

  23.                src: ['**', '!**/.*', '!**/.svn', '!**/.svn/**'],

  24.                //同步到resources/moduleName/texture目录下

  25.                dest: `./assets/resources/${moduleName}/texture`,

  26.                cwd: path.join(UI_ROOT_PATH, moduleName, 'ui', 'headIcon')

  27.            },

  28.            //同步背景图片

  29.            {

  30.                src: ['**', '!**/.*', '!**/.svn', '!**/.svn/**'],

  31.                dest: `./assets/${moduleName}/texture/bg`,

  32.                cwd: path.join(UI_ROOT_PATH, moduleName, 'bg')

  33.            }

  34.        ],

  35.        verbose: true, // 显示日志

  36.        pretend: false, // 模拟输出

  37.        updateAndDelete: true, // 删除dst冗余文件

  38.        compareUsing: 'md5', // 可选'mtime/md5'

  39.        ignoreInDest: ['**/*.meta', '**/*.pac', '**/.svn/**'],  // 不删除.meta文件

  40.    },

  41. }

上面代码中sync-ui就是一个同步任务,其中files数组中配置同步目录,每一个数组元素包含三个字段:

  1. src: 文件过滤器

  2. dest: 目标路径,同步到那里去,以当前Gruntfile文件为相对路径

  3. cwd: 源路径,从那里去复制文件,同样以Gruntfile文件为相对路径

然后是同步选项:

  1. verbose: 打印日志输出,这对我们检查路径是否正确非常有用,建议设置为true

  2. pretend: 同步模拟,当值为真时,配合verbose使用只会显示要同步的文件,不会真实写入或删除文件目标文件

  3. updateAndDelete:删除冗余文件,比如你动随意放入一个图片到客户端ui目录,当执行ui同步资源,这个文件并未在资源仓库中,它会被同步删除掉

  4. compareUsing:文件比较策略,可选项"md5"与“mtime”,建议使用md5保证正确性

  5. ignoreInDest:指定同步时不删除那些文件,这个选项非常有用,我们都知道Cocos Creator会为每个文件生成同名.meta文件,这里一定要注意,不能被同步掉了,除了meta文件外,还有自动图集、svn等文件。下面是执行grunt-shell命令的效果:

  1. ⮀ grunt up-hall --pretend

  2. Running "sync:hall-ui" (sync) task

  3. Copying ../../../hall/ui/hall_btn_jlb.png -> assets/hall/texture/ui/hall_btn_jlb.png

  4. Copying ../../../hall/ui/hall_btn_kefu.png -> assets/hall/texture/ui/hall_btn_kefu.png

  5. Copying ../../../hall/ui/hall_btn_mail.png -> assets/hall/texture/ui/hall_btn_mail.png

  6. Copying ../../../hall/ui/hall_btn_set.png -> assets/hall/texture/ui/hall_btn_set.png

  7. Copying ../../../hall/ui/hall_btn_share.png -> assets/hall/texture/ui/hall_btn_share.png

  8. Copying ../../../hall/ui/hall_Btn_shop.png -> assets/hall/texture/ui/hall_Btn_shop.png

  9. Copying ../../../hall/ui/hall_btn_yxsj.png -> assets/hall/texture/ui/hall_btn_yxsj.png

  10. Copying ../../../hall/ui/hall_img_dianchi.png -> assets/hall/texture/ui/hall_img_dianchi.png

  11. Copying ../../../hall/ui/hall_img_dianliang.png -> assets/hall/texture/ui/hall_img_dianliang.png

  12. Copying ../../../hall/ui/hall_img_gold.png -> assets/hall/texture/ui/hall_img_gold.png

  13. Copying ../../../hall/ui/hall_img_head_00.png -> assets/hall/texture/ui/hall_img_head_00.png

  14. Copying ../../../hall/ui/hall_img_head_01.png -> assets/hall/texture/ui/hall_img_head_01.png

  15. Copying ../../../hall/ui/hall_img_laba.png -> assets/hall/texture/ui/hall_img_laba.png

  16. Copying ../../../hall/ui/hall_img_red.png -> assets/hall/texture/ui/hall_img_red.png

  17. Copying ../../../hall/ui/hall_img_xinhao.png -> assets/hall/texture/ui/hall_img_xinhao.png

  18. Copying ../../../hall/ui/hall_mask_bottom.png -> assets/hall/texture/ui/hall_mask_bottom.png

  19. Unlinking assets/resources/hall/texture/headimg/default.png because it was removed from src.

  20. Unlinking assets/resources/hall/texture/ui/ttz_bg_toast.png because it was removed from src.

  21. Removing dir assets/resources/hall/texture/ui because not longer in src.

上面可以看到以Copying开头的是文件复制信息,使用verbose参数,它显示了从那儿复制文件那儿,Unlinking是删除文件,同样显示了被删除的文件路径。

动画资源同步

上面讲了UI资源的同步, 对于动画资源我们处理方式有些不同,因此需要单独创建一个同步任务:

  1. const syncTask = {

  2.    'sync-ui': { ... }

  3.    'sync-ani': {

  4.         files: [

  5.            //同步UI

  6.            {

  7.                src: ['**', '**/!.DS_Store', '!**/.svn', '!**/.svn/**', '!**/.gitignore'], //过滤器

  8.                dest: `./assets/{moduleName}/animations`,

  9.                cwd: path.join(ANI_ROOT_PATH, moduleName, 'animations')

  10.            },

  11.          ],

  12.          verbose: true, // 显示日志

  13.          pretend: pretend || false, // 模拟输出

  14.          updateAndDelete: true, // 删除dst冗余文件

  15.          compareUsing: 'md5', // 可选'mtime/md5'

  16.          ignoreInDest: ['**/.svn/**', '.DS_Store', '**/.gitignore'],  // 不删除.svn下文件

  17.    },    

  18.    }

  19. }

动画同步与UI同步最大的差别在于,ignoreInDes同步选项不能忽略meta文件。在shawn的项目中,动画是由美术人员在独立的Cocos Creator工程中编辑的,美术人员可以在动画工程中使用Cocos Creator动画编辑器或Spine、DragonBones等动画资源,使用Prefab进行整合,客户端主要依赖美术提供的动画prefab文件以及动画名字,动画同步任务需要将所有动画资源全部同步到客户端项目中,其中包括所有的meta文件。

这里可能会有一个小小的风险,就是动画工程中的meta文件与客户端界面中的meta文件发生UUID冲突,这种冲突的可能性是完全存在的,但在shawn一年半十多个子模块的动画项目中暂时还未遇到过,冲突的概率非常低。

图集合并同步

在UI目录中有一个类特殊的图片,需要做成图集提高游戏渲染性能,在一个游戏项目初期由于UI风格不稳定或使用临时图片,让美术同学经常去合并图集是一个效率较低的事情。因此shawn将需要合并图集的文件放入atlas的子目录中,由程序调用TexturePacker的命令行工具,以atlas子目录为单位生成图集,直接存入客户端模块atlas目录。

图集合并并完全是动态的,shawn编写了一个Node脚本,用于遍历atlas下的子目录文件,生成图集文件,然后再使用grunt-shell插件进行整合,看下面代码:

  1. //TexturePacker图集合并工具

  2. let tpimg = require('../tpimage');

  3. //shell任务

  4. const shellTask = {

  5.    //TexturePacker合并图片

  6.    'tp-img': {

  7.        command() {

  8.            //工作路径,资源仓库atlas

  9.            let cwd = path.join(UI_ROOT_PATH, `${moduleName}/ui/atlas`);

  10.            //目标路径,图集文件保存到这里

  11.            let dst = `./assets/${moduleName}/texture/ui/atlas`;

  12.            //使用编写的Node脚本生成TexturePacker命令数组

  13.            let commands = tpimg.tpdirs({ cwd, dst });


  14.            //模拟输出命令

  15.            if (pretend) {

  16.                console.log(`cwd:${cwd}, dst:${dst}`);

  17.                console.log(commands.join('\n'));

  18.                return '';

  19.            }

  20.            //返回命令字符串,由grunt-shell插件执行

  21.            return commands.join('&&');

  22.        }

  23.    },

  24. }

下面是任务执行时的效果:

两次执行,每一次执行时生成了icon.png、icon.plist,马上再次执行,提示未发生改变没有再重新生成图集,这比我们手动打图或使用Cocos Creator的自动图集效率要高。

资源仓库更新

上面介绍了美术UI、动画、图集等资源的同步,但一个完整的模块资源同步,还需要涉及到对资源仓库的更新,具体操作就是用git或svn将资源仓库更新到最新状态,下面看使用grunt-shell命令更新资源仓库:

  1. //svn更动画资源

  2. 'svn-ui': {

  3.    command: [

  4.        `svn up ${UI_ROOT_PATH}/${moduleName}/ui`,

  5.        `svn up ${UI_ROOT_PATH}/${moduleName}/bg`

  6.    ].json('&&')    

  7. },


  8. //svn更新动画资源

  9. 'svn-ani': {

  10.    //如果使用nosvn参数,返回空字符串,跳过svn更新

  11.    command: nosvn ? '' : `svn up ${ANI_ROOT_PATH}/${moduleName}/animation`,

  12. }

有时会遇到svn暂时不可用,或有同学未安装svn命令行工具,会导致任务失败,可以shawn这里设置了一个nosvn的参数,用于控制是否跳过svn操作,直接返回了一个空字符串。

任务整合

一个子模块完整的资源同步任务大概需要经历下面几个步骤:

我们前面都建立的单个任务,使用grunt.registerTask可以将任意单个任务进行自由组合,看下图:

grunt.registerTask的参数包括:任务名、任务说明、子任务列表,子任务列表是一系列的任务或插件任务字符串的组合,上图中up-hall-svn任务,是由三个shell插件任务组成。

此时我们在项目根目录中,执行: >grunt up-hall即可享受到所有美术图片、动画、音效、配置等等资源的同步到我们的客户端hall模块。

我们经常会遇到这样一个场景:

美术同学:“xxx程序我增加了大厅商店道具张图片,你更新一下呢,我想看看效果”。

程序同学:“这几张图需要与策划配置文件配合才能生效,yyy策划你更新下商店配置”。

策划同学:“今天早上一来我就已经更好了,你直接取吧!”。

动画同学:“商店里几个动画特效我也更新了,你也放进去一下吧”。

程序同学打开终端,键入:grunt up-hall,屏幕一阵疯狂输闪... 1、2、3、4、5 .... 五秒过后,激活Cocos Creator窗口编译资源,观察控制台,一切ok!

程序同学:“你们使用我的IP:7456自己去看吧!”

美术、策划一脸惊讶地看着你,效率这么高...

曾经十几分钟都搞不定的事情,现在几秒就解决了,每天做10几遍都不会觉得累!也不需要做10遍,将grunt任务与Cocos Creator插件结合,嵌入到Cocos Creator界面菜单上面,让程序员多休息一会儿吧!


弓箭大冒险

静待作者分享开发中的心路历程!


奎特尔数字大冒险,点击进入

0.2新版本

优化手写识别、简单难度、可爱新角色、帅气狼头

当然还有满血复活广告!


欢迎关注「奎特尔星球」公众号,欢迎大家投稿,来我们一起成长!

「奎特尔星球」微信公众号

「奎特尔星球」博客网站,建设中...

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存